import collections
import re
import sys

# 编译正则表达式，匹配单词（连续的字母数字字符）
WORD_RE = re.compile(r'\w+')

# 使用 defaultdict 创建字典，默认值为列表
index = collections.defaultdict(list)

# 打开文件并逐行处理
with open(sys.argv[1], encoding='utf-8') as fp:
    for line_no, line in enumerate(fp, 1):  # 从1开始计数行号
        for match in WORD_RE.finditer(line):
            word = match.group()             # 提取匹配的单词
            column_no = match.start() + 1    # 列号从1开始
            location = (line_no, column_no)  # 记录单词位置（行号, 列号）
            index[word].append(location)     # 如果index中当前没有word键，调用default_factory生成缺失的值。这里生成一个空列表，赋值给index[word]，然后返回空列表。因此，.append(location)操作始终成功。如未提供default_factory，遇到缺失的键时，则像往常一样抛出KeyError。

# 按字母顺序（不区分大小写）输出结果
for word in sorted(index, key=str.upper):
    print(word, index[word])


##########################通过__missing__方法解决########################
class StrKeyDict0(dict):  # 用户自定义映射类型最好继承collections.UserDict类，而不要继承dict类。这里继承dict类是为了说明内置的dict.__getitem__方法支持__missing__。继承UserDict类不需要重写get方法，因为继承自UserDict的get方法调用__getitem__。
    def __missing__(self, key):
        """当 key 不存在时调用，处理非字符串类型的 key"""
        if isinstance(key, str):#检查key是不是str类型。如果是，并且找不到这个键，则抛出KeyError。如果不做这个测试，则__missing__方法可以处理任何类型的键，是不是字符串都无所谓，但前提是str(k)得到的是现有键。然而，倘若str(k)得到的键不存在，那就进入了无限递归：__missing__方法的最后一行self[str(key)]将调用__getitem__方法，转而又调用__missing__方法。
            raise KeyError(key)  # 避免无限递归
        return self[str(key)]  # 尝试用字符串形式的 key 查找

    def get(self, key, default=None):
        """重写 get 方法，保持与 __getitem__ 行为一致"""
        try:
            return self[key]  # 调用 __getitem__，可能触发 __missing__
        except KeyError:
            return default  # 找不到 key 时返回默认值

    def __contains__(self, key):
        """重写 in 操作符行为，检查字符串或非字符串形式的 key"""
        return key in self.keys() or str(key) in self.keys()